package com.mangofactory.aop.configuration
{
	import com.mangofactory.aop.aspects.Aspect;
	import com.mangofactory.aop.configuration.tag.MetatagAspectDefinition;
	import com.mangofactory.aop.interceptors.AspectInvokingInterceptor;
	
	import org.as3commons.bytecode.proxy.ProxyFactory;
	import org.as3commons.reflect.Method;
	import org.as3commons.reflect.Type;

	public class AspectContainerBuilder
	{
		private var classRegistry:ClassRegistry;
		private var metatagPolicies:MetatagProxyPolicy = new MetatagProxyPolicy();
		private var proxyPolicies:Vector.<ProxyPolicy> = new Vector.<ProxyPolicy>();
		
		private var _proxyFactory:ProxyFactory;
		
		public function get proxyFactory():ProxyFactory
		{
			return _proxyFactory;
		}

		public function AspectContainerBuilder(classRegistry:ClassRegistry=null,proxyFactory:ProxyFactory=null)
		{
			this.classRegistry = classRegistry || new ExplicitClassRegistry();
			_proxyFactory = proxyFactory || new ProxyFactory();
			proxyPolicies.push(metatagPolicies);
		}
		public function addProxyPolicy(proxyPolicy:ProxyPolicy):AspectContainerBuilder
		{
			proxyPolicies.push(proxyPolicy);
			return this;
		}
		public function addMetatagAspect(definition:MetatagAspectDefinition):AspectContainerBuilder
		{
			metatagPolicies.register(definition.metatag,definition.aspect);
			return this;
		}
		
		public function build():AspectContainer
		{
			var proxyDefinitions:Vector.<ClassProxyDefinition> = buildProxyDefinitions();
			var container:AspectContainer = new AspectContainer(proxyFactory);
			container.addProxyDefinitions(proxyDefinitions);
			container.initalize();
			return container;
		}

		private function buildProxyDefinitions():Vector.<ClassProxyDefinition>
		{
			var result:Vector.<ClassProxyDefinition> = new Vector.<ClassProxyDefinition>();
			var classes:Vector.<Class> = classRegistry.getClasses();
			for each (var clazz:Class in classes)
			{
				result.push(getProxyDefinition(clazz));
			}
			return result;
		}
		public function getProxyDefinition(clazz:Class):ClassProxyDefinition
		{
			var type:Type = Type.forClass(clazz);
			var proxyDefinition:ClassProxyDefinition = new ClassProxyDefinition(type);
			
			// TODO : Should this call requiresProxy(clazz)?
			// I think I'm getting some funcky crossed up logic here
			// and the responsibilities of classRegistry vs AspectContainer vs xxxx 
			// is getting screwed up.
			
			for each (var policy:ProxyPolicy in proxyPolicies)
			{
				if (!policy.requiresProxy(clazz))
					continue;
				var methods:Vector.<Method> = policy.getMethodsToProxy(type);
				for each (var method:Method in methods)
				{
					var interceptor:AspectInvokingInterceptor = getInterceptorFromProxyDefinition(proxyDefinition,method.name);
					var aspects:Vector.<Aspect> = policy.getAspectsForMethod(method);
					interceptor.addAspects(aspects);
				}
			}
			return proxyDefinition;
		}

		private function getInterceptorFromProxyDefinition(proxyDefinition:ClassProxyDefinition, methodName:String):AspectInvokingInterceptor
		{
			var interceptor:AspectInvokingInterceptor = proxyDefinition.getInterceptor(methodName);
			if (!interceptor)
			{
				interceptor = new AspectInvokingInterceptor();
				proxyDefinition.registerMethodProxy(new MethodProxyDefinition(methodName,interceptor));
			}
			return interceptor;
		}
		
		public static function forClasses(classes:Array):AspectContainerBuilder
		{
			var classRegistry:ExplicitClassRegistry = new ExplicitClassRegistry();
			for each (var clazz:Class in classes)
			{
				classRegistry.addClass(clazz);
			}
			return new AspectContainerBuilder(classRegistry);
		}

		public function requiresProxy(clazz:Class):Boolean
		{
			// Check to see if we already contain a registry for this class
			// Note - this sshould also check ClassMatchers as well as explicit classes
			if (classRegistry.containsMatchingClass(clazz))
				return true;
			
			for each (var policy:ProxyPolicy in proxyPolicies)
			{
				if (policy.requiresProxy(clazz))
					return true;
			}
			return false;
		}
		
		public function addClass(clazz:Class):AspectContainerBuilder
		{
			classRegistry.addClass(clazz);
			return this;
		}

		public function removeAllPolicies():void
		{
			metatagPolicies = new MetatagProxyPolicy();
			proxyPolicies = new Vector.<ProxyPolicy>();			
		}
	}
}